use anyhow::Result;
use std::collections::HashMap;
use std::path::Path;
use tracing::{info, error, warn};

use super::api::{Plugin, PluginInfo};

/// Plugin manager for loading and managing plugins
#[derive(Debug)]
pub struct PluginManager {
    plugin_directory: String,
    loaded_plugins: HashMap<String, PluginHandle>,
    plugin_registry: HashMap<String, PluginMetadata>,
}

/// Plugin handle for managing loaded plugins
#[derive(Debug)]
pub struct PluginHandle {
    pub info: PluginInfo,
    pub library: Option<libloading::Library>,
    pub is_active: bool,
}

/// Plugin metadata
#[derive(Debug, Clone)]
pub struct PluginMetadata {
    pub name: String,
    pub version: String,
    pub description: String,
    pub author: String,
    pub file_path: String,
    pub dependencies: Vec<String>,
    pub api_version: String,
}

/// Plugin loading error types
#[derive(Debug, thiserror::Error)]
pub enum PluginError {
    #[error("Plugin not found: {0}")]
    NotFound(String),
    #[error("Plugin already loaded: {0}")]
    AlreadyLoaded(String),
    #[error("Plugin loading failed: {0}")]
    LoadingFailed(String),
    #[error("Plugin API version mismatch: expected {expected}, got {actual}")]
    ApiVersionMismatch { expected: String, actual: String },
    #[error("Plugin dependency not satisfied: {0}")]
    DependencyNotSatisfied(String),
}

impl PluginManager {
    /// Create a new plugin manager
    pub fn new() -> Self {
        Self {
            plugin_directory: "plugins".to_string(),
            loaded_plugins: HashMap::new(),
            plugin_registry: HashMap::new(),
        }
    }
    
    /// Set the plugin directory
    pub fn set_plugin_directory(&mut self, directory: String) {
        self.plugin_directory = directory;
    }
    
    /// Scan for available plugins in the plugin directory
    pub async fn scan_plugins(&mut self) -> Result<()> {
        info!("Scanning for plugins in: {}", self.plugin_directory);
        
        let plugin_dir = Path::new(&self.plugin_directory);
        if !plugin_dir.exists() {
            warn!("Plugin directory does not exist: {}", self.plugin_directory);
            return Ok(());
        }
        
        let mut entries = tokio::fs::read_dir(plugin_dir).await?;
        
        while let Some(entry) = entries.next_entry().await? {
            let path = entry.path();
            
            // Look for dynamic libraries
            if let Some(extension) = path.extension() {
                let ext_str = extension.to_string_lossy();
                if ext_str == "so" || ext_str == "dll" || ext_str == "dylib" {
                    if let Err(e) = self.register_plugin_from_path(&path).await {
                        error!("Failed to register plugin {:?}: {}", path, e);
                    }
                }
            }
        }
        
        Ok(())
    }
    
    /// Register a plugin from a file path
    async fn register_plugin_from_path(&mut self, path: &Path) -> Result<()> {
        let path_str = path.to_string_lossy().to_string();
        info!("Registering plugin from: {}", path_str);
        
        // TODO: Load plugin metadata from a companion .toml file
        // For now, create basic metadata from filename
        let name = path.file_stem()
            .and_then(|s| s.to_str())
            .unwrap_or("unknown")
            .to_string();
        
        let metadata = PluginMetadata {
            name: name.clone(),
            version: "1.0.0".to_string(),
            description: format!("Plugin loaded from {}", path_str),
            author: "Unknown".to_string(),
            file_path: path_str,
            dependencies: vec![],
            api_version: "1.0".to_string(),
        };
        
        self.plugin_registry.insert(name, metadata);
        
        Ok(())
    }
    
    /// Load a plugin by name
    pub async fn load_plugin(&mut self, name: &str) -> Result<Box<dyn Plugin>> {
        info!("Loading plugin: {}", name);
        
        if self.loaded_plugins.contains_key(name) {
            return Err(PluginError::AlreadyLoaded(name.to_string()).into());
        }
        
        let metadata = self.plugin_registry.get(name)
            .ok_or_else(|| PluginError::NotFound(name.to_string()))?;
        
        // Check dependencies
        for dep in &metadata.dependencies {
            if !self.loaded_plugins.contains_key(dep) {
                return Err(PluginError::DependencyNotSatisfied(dep.clone()).into());
            }
        }
        
        // Load the dynamic library
        let library = unsafe {
            libloading::Library::new(&metadata.file_path)
                .map_err(|e| PluginError::LoadingFailed(e.to_string()))?
        };
        
        // TODO: Get plugin entry point and create plugin instance
        // This is a placeholder implementation
        let plugin_info = PluginInfo {
            name: metadata.name.clone(),
            version: metadata.version.clone(),
            description: metadata.description.clone(),
            author: metadata.author.clone(),
            api_version: metadata.api_version.clone(),
        };
        
        let handle = PluginHandle {
            info: plugin_info.clone(),
            library: Some(library),
            is_active: true,
        };
        
        self.loaded_plugins.insert(name.to_string(), handle);
        
        // Create a placeholder plugin instance
        Ok(Box::new(PlaceholderPlugin { info: plugin_info }))
    }
    
    /// Unload a plugin by name
    pub fn unload_plugin(&mut self, name: &str) -> Result<()> {
        info!("Unloading plugin: {}", name);
        
        if let Some(mut handle) = self.loaded_plugins.remove(name) {
            handle.is_active = false;
            // Library will be dropped automatically
            Ok(())
        } else {
            Err(PluginError::NotFound(name.to_string()).into())
        }
    }
    
    /// Get information about a loaded plugin
    pub fn get_plugin_info(&self, name: &str) -> Option<&PluginInfo> {
        self.loaded_plugins.get(name).map(|handle| &handle.info)
    }
    
    /// List all available plugins
    pub fn list_available_plugins(&self) -> Vec<&PluginMetadata> {
        self.plugin_registry.values().collect()
    }
    
    /// List all loaded plugins
    pub fn list_loaded_plugins(&self) -> Vec<&PluginInfo> {
        self.loaded_plugins.values().map(|handle| &handle.info).collect()
    }
    
    /// Check if a plugin is loaded
    pub fn is_plugin_loaded(&self, name: &str) -> bool {
        self.loaded_plugins.contains_key(name)
    }
    
    /// Get the number of loaded plugins
    pub fn loaded_plugin_count(&self) -> usize {
        self.loaded_plugins.len()
    }
    
    /// Enable a plugin
    pub fn enable_plugin(&mut self, name: &str) -> Result<()> {
        if let Some(handle) = self.loaded_plugins.get_mut(name) {
            handle.is_active = true;
            info!("Plugin enabled: {}", name);
            Ok(())
        } else {
            Err(PluginError::NotFound(name.to_string()).into())
        }
    }
    
    /// Disable a plugin
    pub fn disable_plugin(&mut self, name: &str) -> Result<()> {
        if let Some(handle) = self.loaded_plugins.get_mut(name) {
            handle.is_active = false;
            info!("Plugin disabled: {}", name);
            Ok(())
        } else {
            Err(PluginError::NotFound(name.to_string()).into())
        }
    }
    
    /// Check if a plugin is active
    pub fn is_plugin_active(&self, name: &str) -> bool {
        self.loaded_plugins.get(name)
            .map(|handle| handle.is_active)
            .unwrap_or(false)
    }
}

/// Placeholder plugin implementation for testing
#[derive(Debug)]
struct PlaceholderPlugin {
    info: PluginInfo,
}

impl Plugin for PlaceholderPlugin {
    fn info(&self) -> PluginInfo {
        self.info.clone()
    }
    
    fn initialize(&mut self) -> Result<()> {
        info!("Initializing placeholder plugin: {}", self.info.name);
        Ok(())
    }
    
    fn shutdown(&mut self) -> Result<()> {
        info!("Shutting down placeholder plugin: {}", self.info.name);
        Ok(())
    }
    
    fn execute(&self, _command: &str, _args: &[String]) -> Result<String> {
        Ok("Placeholder plugin executed".to_string())
    }
}

impl Default for PluginManager {
    fn default() -> Self {
        Self::new()
    }
}
